package com.project.common.core.utils.redis;

import com.project.common.core.utils.StringUtil;
import io.lettuce.core.SetArgs;
import io.lettuce.core.cluster.api.async.RedisClusterAsyncCommands;
import org.apache.commons.lang.exception.ExceptionUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.connection.RedisConnection;
import org.springframework.data.redis.core.RedisCallback;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.data.redis.core.script.RedisScript;
import org.springframework.stereotype.Component;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisCluster;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.UUID;

/**
 * Redis分布式锁
 *
 * @author light
 * @date 2020-06-30 11:08
 **/
@Component
public class RedisDistributedLock {

    private final Logger log = LoggerFactory.getLogger(RedisDistributedLock.class);

    @Autowired
    private RedisTemplate<String, Object> redisTemplate;

    //设置的LUA脚本
    public static final String UNLOCK_LUA;

    public static final Long SUCCESS = 1L;

    static {
        StringBuilder lockBulider = new StringBuilder();
        //lua脚本
        lockBulider.append("if redis.call(\"get\",KEYS[1] == ARGV[1]) ");
        lockBulider.append("then ");
        lockBulider.append("    return redis.call(\"del\",KEYS[1]) ");
        lockBulider.append("else ");
        lockBulider.append("    return 0 ");
        lockBulider.append("end ");

        UNLOCK_LUA = lockBulider.toString();
    }

    /**
     * 获取锁
     *
     * @param lockKey
     * @param value
     * @param expireTime
     * @return boolean
     * @author light
     * @date 2020-07-01 13:42
     * @params
     **/
    public boolean getLock(String lockKey, String value, int expireTime) {
        try {
            String luaLack = "if redis.call('setNx',KEYS[1],ARGV[1]) then if redis.call('get',KEYS[1])==ARGV[1] then return redis.call('expire',KEYS[1],ARGV[2]) else return 0 end end";
            RedisScript<String> redisScript = new DefaultRedisScript<>(luaLack, String.class);

            log.info("===========redisScript=====" + redisScript.getSha1());
            Object result = redisTemplate.execute(redisScript, Collections.singletonList(lockKey), value, expireTime);
            log.info("===========result========" + redisTemplate.execute(redisScript, Collections.singletonList(lockKey), value, expireTime));

            if (SUCCESS.equals(result)) {
                return Boolean.TRUE;
            }

            return Boolean.TRUE;
        } catch (Exception e) {
            log.error("redis getLock occured an exception" + e);
        }

        return Boolean.FALSE;
    }

    /**
     * 释放锁
     *
     * @param lockKey
     * @param value
     * @return boolean
     * @author light
     * @date 2020-07-01 14:50
     * @params
     **/
    public boolean releaseLock(String lockKey, String value) {
        String luaLack = "if redis.call('get',KEYS[1]) == ARGV[1] then return redis.call('del',KEYS[1]) else return 0 end";
        RedisScript<String> redisScript = new DefaultRedisScript<>(luaLack, String.class);

        Object result = redisTemplate.execute(redisScript, Collections.singletonList(lockKey), value);

        if (SUCCESS.equals(result)) {
            return Boolean.TRUE;
        }

        return Boolean.FALSE;
    }


//    TODO  由于java.lang.ClassCastException: io.lettuce.core.RedisAsyncCommandsImpl cannot be cast to redis.clients.jedis.JedisCommands异常作废

    /**
     * 设置lock锁
     *
     * @param key    lock锁key
     * @param expire 过期时间
     * @return boolean
     * @author light
     * @date 2020-06-30 11:32
     * @params
     **/
    public String setLockB(String key, long expire) {
        try {
            RedisCallback<String> callback = (RedisConnection connection) -> {
                RedisClusterAsyncCommands commands = (RedisClusterAsyncCommands) connection.getNativeConnection();
                String uuid = UUID.randomUUID().toString();

                Object result = commands.set(key, uuid, SetArgs.Builder.nx().ex(expire));

                log.info(result.toString());

                return result.toString();
//                return commands.set(key, uuid, "NX", "PX", expire);
            };

            String result = redisTemplate.execute(callback);

            return !StringUtil.isEmpty(result) + "";
        } catch (Exception e) {
            e.printStackTrace();
            log.error("set redis occured an exception", ExceptionUtils.getFullStackTrace(e));
        }

        return false + "";
    }

    /**
     * 获取lock锁
     * @author light
     * @date 2020-06-30 11:50
     * @params
     * @param key
     * @return java.lang.String
     **/
//    public String getB(String key){
//        try {
//            RedisCallback<String> callback = (connection) -> {
//              JedisCommands commands = (JedisCommands) connection.getNativeConnection();
//
//              return commands.get(key);
//            };
//
//            return redisTemplate.execute(callback);
//        }catch (Exception e){
//            e.printStackTrace();
//            log.error("get redis occured an exception", ExceptionUtils.getFullStackTrace(e));
//        }
//
//        return "";
//    }

    /**
     * 释放lock锁
     *
     * @param key
     * @param requestId 通过get()方法回去的值
     * @return boolean
     * @author light
     * @date 2020-06-30 13:14
     * @params
     **/
    public boolean releaseLockB(String key, String requestId) {
        try {
            List<String> keys = new ArrayList<>();
            keys.add(key);

            List<String> requestIds = new ArrayList<>();
            requestIds.add(requestId);

            RedisCallback<Long> callback = (connection) -> {
                Object nativeConnection = connection.getNativeConnection();

                //区分集群和单机
                //集群模式
                if (nativeConnection instanceof JedisCluster) {
                    return (Long) ((JedisCluster) nativeConnection).eval(UNLOCK_LUA, keys, requestIds);
                }

                //单机模式
                if (nativeConnection instanceof Jedis) {
                    return (Long) ((Jedis) nativeConnection).eval(UNLOCK_LUA, keys, requestIds);
                }

                return 0L;
            };

            Long result = redisTemplate.execute(callback);

            return result != null && result > 0;
        } catch (Exception e) {
            e.printStackTrace();
            log.error("release reids loc occured an exception", ExceptionUtils.getFullStackTrace(e));
        } finally {
            // 清楚ThreadLock中的数据,避免内存溢出
//            lockFlag.remove;
        }

        return false;
    }


}
